/*INCLUDES *******************************************************************/
#include "ses_timer.h"
#include "ses_scheduler.h"
#include  <avr/io.h>
#include "ses_lcd.h"

/* TYPES *********************************************************************/
typedef struct {
	task_t task;
	uint16_t expire;
	uint16_t period;
} taskDescriptor_t;

/* GLOBAL VARIABLES *********************************************************/
volatile taskDescriptor_t tasks[SCHEDULER_ENTRIES];

static volatile uint32_t systime_ms = 0; //Variable for saving SysTime in milliseconds

/*FUNCTION DEFINITION ********************************************************/

/*
 * Update the scheduler after every 1ms.
 * If task is not NULL, reduce the expire time by 1.
 */
void scheduler_update(void) {

	for (uint8_t i = 0; i < SCHEDULER_ENTRIES; i++) {
		if (tasks[i].task != NULL ) {
			if (tasks[i].expire > 0) {
				tasks[i].expire -= 1;
			}
		}
	}
}

//Initialize all the scheduler entries to NULL.
void scheduler_init() {
	for (uint8_t i = 0; i < SCHEDULER_ENTRIES; i++) {
		tasks[i].task = NULL;
	}

}

/*
 * This function runs continuously.
 * Run the task if expire time is zero.
 * Run the task periodically, if period > 0,
 * else remove the task from the scheduler.
 * Enable/disable interrupts to avoid crash i.e sei()/cli().
 */
void scheduler_run() {
	task_t tmptask;
	while (1) {
		cli();
		for (uint8_t i = 0; i < SCHEDULER_ENTRIES; i++) {

			if ((tasks[i].task != NULL )&& (tasks[i].expire == 0)){
			tmptask = tasks[i].task;
			sei();
			(*tmptask)();
			cli();

			if (tasks[i].period > 0) {
				tasks[i].expire = tasks[i].period;
			} else {
				scheduler_remove(tasks[i].task);
			}
		}
	}
		sei();
	}
}

/*
 * Add task to the scheduler.
 * Return False if task is allocated in scheduler.
 *
 * @param task- pointer to the task
 * 		  expire - delay time(in ms) for the task to begin
 * 		  period - 0 if non periodic, else periodic task
 * @return  FALSE = no error, TRUE = scheduler full/error
 */bool scheduler_add(task_t task, uint16_t expire, uint16_t period) {
//Check whether task is already in scheduler.
	cli();
	for (uint8_t i = 0; i < SCHEDULER_ENTRIES; i++) {
		if (tasks[i].task == task) {
			return false;
		}
	}
//Check  scheduler entries to allocate task in one empty "room".
	for (uint8_t i = 0; i < SCHEDULER_ENTRIES; i++) {
		if (tasks[i].task == NULL ) {
			tasks[i].task = task;
			tasks[i].expire = expire;
			tasks[i].period = period;
			return false;
		}
	}
	sei();
	return true;
}

/*
 * Remove task from scheduler.
 * Avoid interrupts while removing task.
 *
 * @param task - valid pointer to the task.
 */
void scheduler_remove(task_t task) {
	for (uint8_t i = 0; i < SCHEDULER_ENTRIES; i++) {
		cli();
		if (tasks[i].task == task) {
			tasks[i].task = NULL;
			tasks[i].expire = 0;
			tasks[i].period = 0;
			break;
		}
	}
	sei();
}

/**
 * a Function for returning time in milliseconds
 * by reading the time from @systime_ms
 */
uint32_t scheduler_getTime() {
	uint32_t readtime = 0;

	cli();

	readtime = systime_ms;

	sei();
	return readtime;

}

//Increment systime_ms one Millisecond at a time
void sysms_increment() {
	systime_ms++;
}

/**
 * sets system time in milliseconds*
 */
void scheduler_setTime(uint32_t abd) {

	cli();

	systime_ms = abd;

	sei();
}

/**
 * get system time in Real Time Clock format
 * using @ms_convertTime
 * to convert from 32 bits integer to RTC type
 *
 */
void scheduler_getTimeRTC(struct type_timeRTC *time) {
	uint32_t systime;

	cli();

	systime = scheduler_getTime();
	ms_convertTime(systime, time);

	sei();
}

/**
 * Sets system time from Real Time Clock
 * into an uint32_t variable
 * Then use @scheduler_setTime to set @systime_ms
 */
void scheduler_setTimeRTC(const struct type_timeRTC *time) {
	uint32_t temp;

	cli();
	//extract the time from RTC-structure
	temp = (time->milli);
	temp += SEC_MS * (time->second);
	temp += MIN_MS * (time->minute);
	temp += HOUR_MS * (time->hour);
	//set Time using the extracted Time
	scheduler_setTime(temp);

	sei();

}

/**
 * @ms_convertTime role to convert system Time from integer format
 * into a RTC type constructed of
 */
void ms_convertTime(uint32_t mstime, struct type_timeRTC *time) {
	uint32_t temp_ms, temp_sec, temp_min, temp_hour;

	//Convert time from long 32 bits to Hour, Minutes, Seconds, Milliseconds
	temp_hour = (((mstime / SEC_MS ) / HOUR_MIN ) / HOUR_MIN );
	temp_min = ((mstime / SEC_MS ) / HOUR_MIN ) % HOUR_MIN;
	temp_sec = mstime / SEC_MS - temp_min * HOUR_MIN
			- temp_hour * HOUR_MIN * HOUR_MIN;
	temp_ms = mstime % SEC_MS;

	//Save the converted time into the RTC-type variable
	(time->milli) = temp_ms;
	(time->second) = temp_sec;
	(time->minute) = temp_min;
	if (temp_hour >= 24)
		temp_hour %= 24;
	(time->hour) = temp_hour;
}

